package org.hepeng.workx.web.filter;

import lombok.Data;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.hepeng.workx.web.http.BodyCachingHttpServletRequestWrapper;
import org.hepeng.workx.web.http.BodyCachingHttpServletResponseWrapper;
import org.hepeng.workx.web.util.HttpRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static org.springframework.http.MediaType.*;


/**
 * @author he peng
 */

@Data
public class LoggingFilter implements Filter {

    private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class);


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        boolean isFirstRequest = !isAsyncDispatch(httpRequest);
        boolean shouldLog = shouldLog(httpRequest);

        if (shouldLog && isFirstRequest) {
            beforeRequest(httpRequest , httpResponse);
        }

        try {

            chain.doFilter(request , response);
        } finally {
            if (shouldLog && !isAsyncStarted(httpRequest)) {
                afterRequest(httpRequest , httpResponse);
            }
        }
    }

    @Override
    public void destroy() {

    }

    private boolean isAsyncStarted(HttpServletRequest request) {
        return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
    }

    private boolean isAsyncDispatch(HttpServletRequest request) {
        return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
    }

    private void beforeRequest(HttpServletRequest request , HttpServletResponse response) {
        try {
            LOG.info(createRequestLogMessage(request));
        } catch (Throwable t) {
            LOG.warn("Logging Before Request Failed" , t);
        }
    }

    protected void afterRequest(HttpServletRequest request , HttpServletResponse response) {
        try {
            LOG.info(createResponseLogMessage(request , response));
        } catch (Throwable t) {
            LOG.warn("Logging After Request Failed" , t);
        }
    }

    protected String createRequestLogMessage(HttpServletRequest request) {
        request.setAttribute(LoggingFilter.class.getName() + ".StartTimestamp" , System.nanoTime());
        HttpSession session = request.getSession(false);
        StringBuilder logMsgBuilder =
                new StringBuilder("\r\n ******************** Http Request ******************** \r\n")
                .append("Request URL : ").append(request.getRequestURL())
                .append("\r\n")
                .append("Method : ").append(request.getMethod())
                .append("\r\n")
                .append("Session ID : ").append(Objects.nonNull(session) ? session.getId() : "null")
                .append("\r\n")
                .append("Client IP : ").append(HttpRequestUtils.getClientIP(request))
                .append("\r\n")
                .append("Parameters : ");

        Map<String, String[]> parameterMap = request.getParameterMap();
        if (MapUtils.isNotEmpty(parameterMap)) {
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                logMsgBuilder.append("\r\n  ")
                        .append(entry.getKey()).append(" : ").append(Arrays.deepToString(entry.getValue()));
            }
        }

        logMsgBuilder.append("\r\n").append("Http Headers : ");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (! org.apache.commons.lang3.StringUtils.equalsIgnoreCase("cookie" , headerName)) {
                logMsgBuilder.append("\r\n  ")
                        .append(headerName).append(" : ").append(request.getHeader(headerName));
            }
        }

        logMsgBuilder.append("\r\nCookie : ");
        Cookie[] cookies = request.getCookies();
        if (ArrayUtils.isNotEmpty(cookies)) {
            for (Cookie cookie : cookies) {
                logMsgBuilder.append("\r\n  ").append(cookie.getName())
                        .append(" = ").append(cookie.getValue());
            }
        }


//        if (MapUtils.isNotEmpty(headers)) {
//            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
//                if (! org.apache.commons.lang3.StringUtils.equalsIgnoreCase("cookie" , entry.getKey())) {
//                    logMsgBuilder.append("\r\n  ")
//                            .append(entry.getKey()).append(" : ").append(entry.getValue());
//                }
//            }
//
//            List<String> cookies = headers.get("cookie");
//            if (CollectionUtils.isNotEmpty(cookies)) {
//                logMsgBuilder.append("\r\nCookie : ");
//                for (String cookie : cookies) {
//                    for (String c : org.apache.commons.lang3.StringUtils.split(cookie, ";")) {
//                        logMsgBuilder.append("\r\n  ").append(org.apache.commons.lang3.StringUtils.trim(c));
//                    }
//                }
//            }
//        }

//        if (LOG.isDebugEnabled() && ! HttpRequestUtils.isMultipart(request)) {
//            logMsgBuilder.append("\r\n").append("Payload : ").append(getRequestPayload(request));
//        }

        return logMsgBuilder.toString();
    }

//    private String getRequestPayload(HttpServletRequest request) {
//        try {
//            BodyCachingHttpServletRequestWrapper readableRequest = new BodyCachingHttpServletRequestWrapper(request);
//            return StringUtils.newStringUtf8(readableRequest.getBody());
//        } catch (IOException e) {
//            LOG.error("read request payload failed" , e);
//        }
//        return "";
//    }

    private String getResponsePayload(BodyCachingHttpServletResponseWrapper responseWrapper) {
        return StringUtils.newString(responseWrapper.getBody() , responseWrapper.getCharacterEncoding());
    }

    protected String createResponseLogMessage(HttpServletRequest request , HttpServletResponse response) {
        Long startTimestamp = (Long) request.getAttribute(LoggingFilter.class.getName() + ".StartTimestamp");
        long currentNanoTimestamp = System.nanoTime();
        long time = TimeUnit.NANOSECONDS.toMillis(currentNanoTimestamp - startTimestamp);
        HttpSession session = request.getSession(false);

        StringBuilder logMsgBuilder = new StringBuilder("\r\n ******************** Http Response ******************** \r\n")
                .append("Request URL : ").append(request.getRequestURL())
                .append("\r\n")
                .append("Method : ").append(request.getMethod())
                .append("\r\n")
                .append("Http Status : ").append(response.getStatus())
                .append("\r\n")
                .append("Time : ").append(time).append(" ms")
                .append("\r\n")
                .append("Session ID : ").append(Objects.nonNull(session) ? session.getId() : "null")
                .append("\r\n")
                .append("Headers : ");


        Map<String , String> headers = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        if (CollectionUtils.isNotEmpty(headerNames)) {
            for (String headerName : headerNames) {
                headers.put(headerName , response.getHeader(headerName));
            }
        }
        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                if (! org.apache.commons.lang3.StringUtils.equalsIgnoreCase("set-cookie" , entry.getKey())) {
                    logMsgBuilder.append("\r\n  ")
                            .append(entry.getKey()).append(" : ").append(entry.getValue());
                }
            }

            logMsgBuilder.append("\r\nSet-Cookie : ");
            String[] setCookies = org.apache.commons.lang3.StringUtils.split(headers.get("set-cookie"), ";");
            if (ArrayUtils.isNotEmpty(setCookies)) {
                for (String sc : setCookies) {
                    logMsgBuilder.append("\r\n  ").append(sc);
                }
            }
        }

//        if (LOG.isDebugEnabled() && ! org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase(
//                response.getContentType() , APPLICATION_OCTET_STREAM_VALUE , APPLICATION_PDF_VALUE
//                , IMAGE_GIF_VALUE , IMAGE_JPEG_VALUE , IMAGE_PNG_VALUE
//                , MULTIPART_FORM_DATA_VALUE)) {
//            logMsgBuilder.append("\r\n").append("Payload : ")
//                    .append(getResponsePayload((BodyCachingHttpServletResponseWrapper) response));
//        }

        return logMsgBuilder.toString();
    }

    protected boolean shouldLog(HttpServletRequest request) {
        return LOG.isInfoEnabled();
    }
}
